import defaultOptions from './config'
import html2canvas from 'html2canvas'
import JsPDF from 'jspdf'

class Methods {
  constructor(Vim, options = {}) {
    this.$Vim = Vim
    this.inOptions = Object.assign({}, options)
    this.options = Object.assign({}, defaultOptions, options)
    this.count = 0
    this.init()
  }

  init() {
    this.initPdf()
    this.getA4WH()
    this.createTempCanvas()
  }

  // 初始化 jspdf
  initPdf() {
    let pdfOption = {
      orientation: "p", // Orientation of the first page. Possible values are "portrait" or "landscape" (or shortcuts "p" or "l")
      unit: "pt", // Measurement unit (base unit) to be used when coordinates are specified. Possible values are "pt" (points), "mm", "cm", "in", "px", "pc", "em" or "ex". Note that in order to get the correct scaling for "px" units, you need to enable the hotfix "px_scaling" by setting options.hotfixes = ["px_scaling"]
      format: "a4", // The format of the first page
      floatPrecision: 16, // or "smart", default is 16
    };
    this.options.pdf = new JsPDF(pdfOption);
  }
  // 获取a4纸张大小转换的px
  getA4WH() {
    // 获取设备像素比率
    this.options.ratio = window.devicePixelRatio || 1;

    let arrDPI = this.js_getDPI()
    this.options.xDpi = arrDPI[0];
    this.options.yDpi = arrDPI[1];
    this.options.a4W = this.options.a4[this.options.xDpi][0];
    this.options.a4H = this.options.a4[this.options.yDpi][1];
  }
  // 获取 deviceXYDPI(每英寸水平的点数)
  js_getDPI() {
    let arrDPI = [];
    if (window.screen.deviceXDPI !== undefined) {
      arrDPI[0] = window.screen.deviceXDPI;
      arrDPI[1] = window.screen.deviceYDPI;
    } else {
      let tmpNode = document.createElement("div");
      tmpNode.style.cssText =
        "width:1in;height:1in;position:absolute;left:0px;top:0px;z-index:99;visibility:hidden";
      document.body.appendChild(tmpNode);
      arrDPI[0] = parseInt(tmpNode.offsetWidth.toString());
      arrDPI[1] = parseInt(tmpNode.offsetHeight.toString());
      tmpNode.parentNode.removeChild(tmpNode);
    }
    return arrDPI;
  }
  // 创建一个临时的canvas
  createTempCanvas() {
    let scale = this.options.printOptions.scale;
    this.options.copyCanvas = document.createElement("canvas");
    this.options.copyCanvas.width = this.options.a4W * this.options.ratio*scale; // 同步元素对象生成的 canvas对象宽度和临时的canvas宽度一致，防止绘制的图像模糊和拉伸
    this.options.copyCanvas.height = this.options.a4H * this.options.ratio*scale; // 同步元素对象生成的 canvas对象高度和临时的canvas高度一致，防止绘制的图像模糊和拉伸
    this.options.copyCanvas.style.width = this.options.a4W + 'px'; // 使用 css 设置临时的 canvas宽度和元素对象生成的 canvas对象css宽度一致，防止绘制的图像模糊和拉伸
    this.options.copyCanvas.style.height = this.options.a4H + 'px'; // 使用 css 设置临时的 canvas高度和元素对象生成的 canvas对象css高度一致，防止绘制的图像模糊和拉伸
    this.options.copyContext = this.options.copyCanvas.getContext("2d", { willReadFrequently: true });
    this.options.copyContext.fillStyle = "#ffffff"; // 填充背景默认为白色
    this.options.copyContext.fillRect(0, 0, this.options.copyCanvas.width, this.options.copyCanvas.height); // 填充至整个页面
  }
  // 生成文件
  async generateFile({ homeEl = null, headEl = null, footEl = null, pagesEl = [[]], homeHeaderFlag = false, homeFooterFlag = false, pagesHeaderFlag = true, pagesFooterFlag = true, previewBox = null, number = 0 }) {
    return new Promise(async (resolve, reject) => {
      try {
        if (this.options.showLoading) {
          this.showLoading(`文件生成中...`)
        }
        // 存储html元素
        if(headEl){ // 有自定义头部
          this.options.headEl = headEl
          this.options.homeHeaderFlag = homeHeaderFlag
          this.options.pagesHeaderFlag = pagesHeaderFlag
        } else {
          this.options.homeHeaderFlag = false
          this.options.pagesHeaderFlag = false
        }
        if(footEl){ // 有自定义尾部
          this.options.footEl = footEl
          this.options.homeFooterFlag = homeFooterFlag
          this.options.pagesFooterFlag = pagesFooterFlag
        } else {
          this.options.homeFooterFlag = false
          this.options.pagesFooterFlag = false
        }
        if(homeEl){ // 有首页
          this.options.homeEl = homeEl
        } else {
          this.options.homeFooterFlag = false // 必须在这里判断，防止误传参，因为在底部计算页码的时候用到了，参数错了页面会差一页
        }

        this.options.pagesEl = pagesEl
        // 预览盒子元素对象
        this.options.previewBox = previewBox
        // 存储转换的 canvas
        headEl && (this.options.headCanvas = await html2canvas(headEl,this.options.printOptions));
        footEl && (this.options.footCanvas = await html2canvas(footEl,this.options.printOptions));
        homeEl && (this.options.homeCanvas = await html2canvas(homeEl,this.options.printOptions));

        let pagesCanvas = [];
        for (let i = 0; i < pagesEl.length; i++) {
          let part = []
          let pageEl = pagesEl[i];
          for (let j = 0; j < pageEl.length; j++) {
            let elCvs = await html2canvas(pageEl[j],this.options.printOptions);
            part.push(elCvs)
          }
          pagesCanvas.push(part)
        }
        this.options.pagesCanvas = pagesCanvas;
        await this.dealPages(resolve)
      } catch (e) {
        reject(e)
      }
    })
  }
  // 导出pdf
  async dealPages(resolve) {

    // 绘制首页
    this.options.homeCanvas && this.drawCopy({ canvas: this.options.homeCanvas, headerFlag: this.options.homeHeaderFlag, footerFlag: this.options.homeFooterFlag }); // 绘制副本
    this.options.homeCanvas && this.turnCanvasToPdf(); // 塞入pdf文件里边
    for (let i = 0; i < this.options.pagesCanvas.length; i++) { // 遍历获取每一部分
      this.options.copyCanvasRemainingH = 0
      this.options.currentCanvasRemainingH = 0
      // 如果有首页 或者 没有首页，但是外层 i 不为 0
      if (this.options.homeCanvas || (!this.options.homeCanvas && i > 0)) {
        await this.addPage(); // 先添加一页
      }
      let els = this.options.pagesCanvas[i]
      for (let j = 0; j <= els.length; j++) { // 遍历每一个对象
        let canvas = null;
        if (this.options.currentCanvasRemainingH > 0) { // 如果上一页没画完，接着画上一页剩余的东西，其实这里可以用 currentCanvasRemainingH 来计算的，但是刚开始没想到，于是就这么扔这儿了，想优化的可以把这里修改一下
          await this.addPage();
          j -= 1; // pages 对应的还是上一页
        }
        canvas = els[j]; // 还有这里，可以直接用 pages[i] 来获取，因为在上一行做了 i -= 1 的操作，这里你们看着来就行，我就不做优化了，等封装的时候再做优化
        if (canvas) { // 这里是因为 上边的循环 用了 <= length ，所以最后一次会是 undefined,这里做个中断的操作
          this.drawCopy({ canvas: canvas, headerFlag: this.options.pagesHeaderFlag, footerFlag: this.options.pagesFooterFlag }); // 绘制副本
          this.turnCanvasToPdf(); // 塞入pdf文件里边
        } else {
          break;
        }
      }
    }
    if (this.options.showLoading) {
      this.hideLoading()
    }
    resolve(this);
  }
  // 上传
  uploadFile({ url, filename = '生成的pdf文件.pdf', params = {} }) {
    return new Promise((resolve, reject) => {
      if (this.options.showLoading) {
        this.showLoading('文件上传中...')
      }
      let namesArr = filename.split('.')
      let ext = namesArr[namesArr.length - 1]
      if (ext !== 'pdf') {
        filename += '.pdf'
      }
      let blob = this.options.pdf.output('blob', { filename })
      let tmpFile = new File([blob], filename)
      this.upload(tmpFile, url, params, resolve, reject)
    })
  }
  // 导出
  exportFile(filename = '导出的pdf文件') {
    this.options.pdf.save(filename);
  }
  // 输出
  output({ type = 'blob', filename = '导出的pdf文件' }) {
    return this.options.pdf.output(type, filename)
  }
  // 重置预览
  resetPreviewBox() {
    this.options.previewBox && (this.options.previewBox.innerHTML = '')
    this.resetFile()
  }
  // 重置整个pdf
  resetJsPdf() {
    this.resetFile()
  }
  // 重置删除文件
  resetFile() {
    let totalPage = this.options.totalPage
    while (totalPage > 0) {
      this.options.pdf.deletePage(totalPage)
      totalPage--
    }
    this.options.pdf.addPage()
    this.options.totalPage = 1;
    this.options.currentPage = 1;
    this.options.copyCanvasRemainingH = 0;
    this.options.currentCanvasRemainingH = 0;
  }
  // 绘制副本
  drawCopy({ canvas, headerFlag = false, footerFlag = false }) {
    let diff, // 计算差值
      calcHeight = 0, // 参与计算的高度
      // 准备 canvas 的切片需要的数据，具体参数详见：https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawImage
      dw = {
        sx: 0, // 源x
        sy: 0, // 源y
        sw: 0, // 源w
        sh: 0, // 源h
        dx: 0, // 目标x
        dy: 0, // 目标y
        dw: 0, // 目标w
        dh: 0, // 目标h
      };
    if (this.options.currentCanvasRemainingH > 0) {
      // 如果有剩余 page 说明上一个 canvas 画了一部分，还剩余一部分没画上去，canvas还是之前的那个 canvas 对象
      this.createTempCanvas(); // 创建一个新的临时画布
      calcHeight = this.options.copyCanvas.height;
      if (headerFlag) {
        // 有页眉
        calcHeight -= this.options.headCanvas.height;
      }
      if (footerFlag) {
        // 有页脚
        calcHeight -= this.options.footCanvas.height;
      }
      diff = this.options.currentCanvasRemainingH - calcHeight;
      dw.sx = 0;
      dw.sy = canvas.height - this.options.currentCanvasRemainingH;
      dw.sw = canvas.width;
      dw.dx = 0;
      dw.dy = headerFlag ? this.options.headCanvas.height : 0;
      dw.dw = this.options.copyCanvas.width;
      if (diff > 0) {
        // 当前绘制对象剩余高度大于临时 canvas 对象高度
        dw.sh = calcHeight;
        dw.dh = calcHeight;
        // 因为需要截断当前页面，所以需要计算截断的点位
        let position = this.checkCutPosition(canvas, dw.sh, dw.dy);
        let positionDiff = dw.sh - position
        dw.sh = position
        dw.dh = position
        this.options.currentCanvasRemainingH = diff + positionDiff;
        this.options.copyCanvasRemainingH = 0;
      } else {
        // 当前绘制对象剩余高度小于或者等于临时 canvas 对象高度
        dw.sh = this.options.currentCanvasRemainingH;
        dw.dh = this.options.currentCanvasRemainingH;

        this.options.currentCanvasRemainingH = 0;
        let tmpCopyCanvasRemainingH = Math.abs(diff)
        this.options.copyCanvasRemainingH = tmpCopyCanvasRemainingH < 48 ? 0 : tmpCopyCanvasRemainingH;
      }
    } else {
      // 没有剩余 page 说明这里是一个新的 canvas
      if (this.options.copyCanvasRemainingH > 0) {
        // 如果临时 canvas 对象剩余高度 > 0,说明临时画布没画满，继续在当前画布上画一个新的 canvas 对象
        diff = canvas.height - this.options.copyCanvasRemainingH;
        dw.sx = 0;
        dw.sy = 0;
        dw.sw = canvas.width;
        dw.dx = 0;
        dw.dy =
          this.options.copyCanvas.height -
          this.options.copyCanvasRemainingH -
          (headerFlag ? this.options.headCanvas.height : 0);
        dw.dw = this.options.copyCanvas.width;
        if (diff > 0) {
          // 当前绘制对象高度比临时 canvas 剩余的高度大
          dw.sh = this.options.copyCanvasRemainingH;
          dw.dh = this.options.copyCanvasRemainingH;

          // 因为需要截断当前页面，所以需要计算截断的点位
          let position = this.checkCutPosition(canvas, dw.sh, dw.sy);
          let positionDiff = dw.sh - position
          dw.sh = position
          dw.dh = position

          this.options.currentCanvasRemainingH = diff + positionDiff;
          this.options.copyCanvasRemainingH = 0;
        } else {
          // 当前绘制对象高度比临时 canvas 剩余的高度小或相等
          dw.sh = canvas.height;
          dw.dh = canvas.height;

          this.options.currentCanvasRemainingH = 0;
          let tmpCopyCanvasRemainingH = Math.abs(diff)
          this.options.copyCanvasRemainingH = tmpCopyCanvasRemainingH < 48 ? 0 : tmpCopyCanvasRemainingH;
        }
      } else {
        // 临时 canvas 没有剩余高度，用一个新的 临时 canvas 对象画
        this.createTempCanvas(); // 创建临时画布
        calcHeight = this.options.copyCanvas.height;
        if (headerFlag) {
          // 有页眉
          calcHeight -= this.options.headCanvas.height;
        }
        if (footerFlag) {
          // 有页脚
          calcHeight -= this.options.footCanvas.height;
        }
        diff = canvas.height - calcHeight;
        dw.sx = 0;
        dw.sy = 0;
        dw.sw = canvas.width;
        dw.dx = 0;
        dw.dy = headerFlag ? this.options.headCanvas.height : 0;
        dw.dw = this.options.copyCanvas.width;
        if (diff > 0) {
          dw.sh = calcHeight;
          dw.dh = calcHeight;
          // 因为需要截断当前页面，所以需要计算截断的点位
          let position = this.checkCutPosition(canvas, dw.sh, dw.sy);
          let positionDiff = dw.sh - position
          dw.sh = position
          dw.dh = position

          this.options.currentCanvasRemainingH = diff + positionDiff;
          this.options.copyCanvasRemainingH = 0;
        } else {
          // 当前绘制对象高度比临时 canvas高度小或者相同
          dw.sh = canvas.height;
          dw.dh = canvas.height;

          this.options.currentCanvasRemainingH = 0;
          let tmpCopyCanvasRemainingH = Math.abs(diff)
          this.options.copyCanvasRemainingH = tmpCopyCanvasRemainingH < 48 ? 0 : tmpCopyCanvasRemainingH;
        }
      }
    }
    this.options.copyContext.drawImage(
      canvas,
      dw.sx,
      dw.sy,
      dw.sw,
      dw.sh,
      dw.dx,
      dw.dy,
      dw.dw,
      dw.dh
    );
    if (headerFlag) {
      // 有页眉
      this.options.copyContext.drawImage(
        this.options.headCanvas,
        0,
        0,
        this.options.headCanvas.width,
        this.options.headCanvas.height,
        0,
        0,
        this.options.copyCanvas.width,
        this.options.headCanvas.height
      );
    }
    if (footerFlag) {
      // 有页脚
      this.options.copyContext.drawImage(
        this.options.footCanvas,
        0,
        0,
        this.options.footCanvas.width,
        this.options.footCanvas.height,
        0,
        this.options.copyCanvas.height - this.options.footCanvas.height,
        this.options.copyCanvas.width,
        this.options.footCanvas.height
      );
    }
    // this.$Vim.$refs.showpdf.appendChild(this.options.copyCanvas)
  }
  // 找到当前点位是否可以截断，防止分页的时候文字分成了一半
  checkCutPosition(canvas, start, end) {
    let a4HeightRef = Math.floor(canvas.width / this.options.a4W * this.options.a4H)
    const ctx = canvas.getContext("2d", { willReadFrequently: true })
    let checkRows = 0, // 记录白色行数
      // position = start / this.options.ratio, // 这里需要除以像素比率，否则下方获取不到截断位置的颜色，将全部是白色，不知道什么情况，反正后边除以这个就正常了
      // endPosition = end / this.options.ratio; // 这里需要除以像素比率，否则下方获取不到截断位置的颜色，将全部是白色，不知道什么情况，反正后边除以这个就正常了
      position = start/ this.options.ratio; // 这里需要除以像素比率，否则下方获取不到截断位置的颜色，将全部是白色，不知道什么情况，反正后边除以这个就正常了
    // 从定好的高度页面底部开始循环遍历canvas的每个点，找到可以截断的地方
    for (let i = position; i >= end; i--) {
      let checkCols = 0; // 记录非白色点数
      let isWhite = true; // 是否是白色
      for (let j = 0; j < canvas.width; j++) {
        let canvasData = ctx.getImageData(j, i, 1, 1).data;
        // 如果该单位的颜色不是白色c[0]  c[1]  c[2] 分别代表r g b 255
        if (
          canvasData[0] !== 0xff ||
          canvasData[1] !== 0xff ||
          canvasData[2] !== 0xff
        ) {
          checkCols++; // 非白色 + 1
        }
        // 如果这行有超过10个单位都不是白色，退出当前循环
        if (checkCols > 4) {
          isWhite = false;
          checkCols = 0
          break;
        }
      }
      if (isWhite) {
        checkRows++;
        // 如果有超过20行都是白色的，canvas在这里可以截断了
        if (checkRows >= 20) {
          position = i;
          break;
        }
      } else {
        checkRows = 0;
      }
    }
    // return position * this.options.ratio // 这里需要再乘以像素比率
    return position // 这里需要再乘以像素比率
  }
  // 转换pdf
  turnCanvasToPdf() {
    // 转换 pdf 一只往里边塞入创建的临时 canvas 就行，所有的处理都在临时 canvas 上处理过了
    let canvas = this.options.copyCanvas;
    let cvsWidth = canvas.width;
    let cvsHeight = canvas.height;

    this.options.pdf.addImage(
      canvas.toDataURL("image/jpeg", 1.0),
      "jpeg",
      0,
      0,
      this.options.a4[72][0],
      (this.options.a4[72][0] / cvsWidth) * cvsHeight
    );
    if (this.options.previewBox) {
      this.options.previewBox && this.options.previewBox.appendChild(canvas)
    }
  }
  // 添加一页PDF
  async addPage() {
    this.options.pdf.addPage();
    this.options.totalPage += 1
    this.options.currentPage += 1; // 当前页 + 1
    this.options.footEl && await this.generateFooterCanvas()
    // console.log(this.options.footEl)
  }
  // 生成页脚页码
  async generateFooterCanvas() {
    // 由于要绘制页码，这里需要重新获取一下页脚的 element 并修改页码，再次绘制出来页脚的 canvas
    let pagerEl = this.options.footEl.querySelector("#pageNumber");
    // console.log(pagerEl)
    pagerEl && (pagerEl.innerHTML = this.options.homeFooterFlag ? `${this.options.currentPage-1}` : `${this.options.currentPage}`); // 首页要页脚，这里就正常，首页不要页脚，这里就减去首页的页码 1
    this.options.footCanvas = await html2canvas(this.options.footEl,this.options.printOptions);
  }
  showLoading(title = 'loading...') {
    this.options.loading = this.$Vim.$loading({
      lock: true,
      text: title,
      spinner: 'el-icon-loading',
      background: 'rgba(0, 0, 0, 0.7)'
    })
  }
  hideLoading() {
    if (this.options.loading) {
      this.options.loading.close()
      this.options.loading = null
    }
  }
  // 上传方法
  upload(file, url, others = {}, resolve = () => { }, reject = () => { }) {
    let self = this
    let data = Object.assign({ type: 'file' }, others)
    let formData = new FormData();
    for (let k in data) {
      formData.append(k, data[k]);
    }
    formData.append('file', file);
    let xhr = this.getXhr();
    xhr.open('POST', url, true);
    xhr.withCredentials = true;
    xhr.send(formData);
    // 处理返回数据
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (self.options.showLoading) {
          self.hideLoading()
        }
        if (xhr.status === 200) {
          resolve(JSON.parse(xhr.response))
        } else {
          reject(JSON.parse(xhr.response))
        }
      }
    }
  }
  getXhr() {
    let xhr;
    if (window.XMLHttpRequest) {
      xhr = new XMLHttpRequest();
    } else {
      //为了兼容IE6
      xhr = new ActiveXObject('Microsoft.XMLHTTP');
    }
    return xhr;
  }
}

export default Methods
